Skip to content

feat(theme): scoped theming with ThemeScope component#768

Open
paanSinghCoder wants to merge 1 commit intomainfrom
feat/scoped-theming
Open

feat(theme): scoped theming with ThemeScope component#768
paanSinghCoder wants to merge 1 commit intomainfrom
feat/scoped-theming

Conversation

@paanSinghCoder
Copy link
Copy Markdown
Contributor

@paanSinghCoder paanSinghCoder commented Apr 30, 2026

Summary

Screen.Recording.2026-04-30.at.4.15.38.PM.mov

Check usage in /examples/theme-scoped

Any element with data-theme (or wrapped in <ThemeScope>) creates an isolated theme region. Descendants resolve all --rs-color-* tokens, color-scheme, and the smooth theme-switch transition from the nearest scoped ancestor.

The CSS alias chain (--rs-color-*--rs-neutral-*--rs-gray-*) now redeclares each layer on [data-theme="light"] / [data-theme="dark"] so scopes re-anchor cleanly. Adds matching color-scheme rules so native UI (form controls, scrollbars) follows the scope.

Why a separate ThemeScope (not extending ThemeProvider)?

ThemeProvider bundles global-only concerns: writes to document.documentElement, localStorage persistence, prefers-color-scheme listener, inline SSR-flash script, and an explicit "nested providers are no-ops" guard. Reusing it for scopes would mean gating every effect on an isScope flag, removing the nesting guard, and reworking context — same internal split, just behind one export. ThemeScope is ~30 lines, no state, no effects, does only what scoping needs.

Why no localStorage on ThemeScope?

Stateless by design. Scopes share no semantic identity, so a single storage key wouldn't fit, and per-scope persistence would reintroduce SSR flash at the scope level (each scope would need its own inline pre-hydration script). Consumers manage state themselves; we deferred a useScopedTheme helper hook until there's a real ask.

Test plan

  • pnpm test in packages/raystack (9 new ThemeScope tests pass)
  • Visit /examples/scoped-theme — global and scoped switchers toggle independently; both panels re-theme correctly
  • Bare <div data-theme="dark"> inside a light page — native UI (checkboxes, scrollbars) follows the scope

🤖 Generated with Claude Code

Any element with `data-theme` (or wrapped in `<ThemeScope>`) now creates an isolated theme region. Descendants resolve all `--rs-color-*` tokens, `color-scheme`, and the smooth theme-switch transition from the nearest scoped ancestor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 30, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview, Comment Apr 30, 2026 10:43am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 30, 2026

📝 Walkthrough

Walkthrough

The PR introduces a ThemeScope component that enables applying theme settings (theme, accent color, gray color, and style variant) to a specific subtree of the application. The component wraps an element and applies data-theme and related data-* attributes, which serve as the source for CSS variable resolution within that scoped region. Supporting CSS files are updated to scope theme-related color variables to elements with data-theme attributes instead of :root, and new types are exported to centralize allowed theme configuration values. Documentation and tests are included.

Possibly related issues

Suggested reviewers

  • rsbh
  • rohanchkrabrty
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: introducing scoped theming via a new ThemeScope component, which is the primary feature added across the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description clearly explains the feature, rationale, and testing approach, all directly related to the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/www/src/app/examples/scoped-theme/page.tsx`:
- Around line 17-23: The panelStyle object uses a spacing token for borderRadius
(borderRadius: 'var(--rs-space-3)') which teaches the wrong token family; update
the borderRadius value to use the appropriate radius token (e.g., replace
var(--rs-space-3) with the project's radius token such as var(--rs-radius-3) or
the correct --rs-radius-<n>), keeping the rest of panelStyle unchanged so the
example demonstrates the proper radius token usage.

In `@packages/raystack/styles/primitives/appearance.css`:
- Around line 13-20: The CSS unconditionally applies color-scheme when
[data-theme] is present, bypassing ThemeProvider's enableColorScheme flag;
update the rules to only apply when an explicit opt-in marker is present (e.g.,
[data-enable-color-scheme="true"] or a class like .enable-color-scheme) so
ThemeProvider's enableColorScheme prop in ThemeProvider
(packages/raystack/components/theme-provider/theme.tsx) can control whether
native color-scheme is set; ensure the marker is added/removed by ThemeProvider
where enableColorScheme is read so disabling the prop actually prevents the CSS
color-scheme rules from applying.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 68a8df63-d9e9-4b91-beba-bc0d1b81312d

📥 Commits

Reviewing files that changed from the base of the PR and between a7e6705 and 64e762a.

📒 Files selected for processing (13)
  • apps/www/src/app/examples/scoped-theme/page.tsx
  • apps/www/src/content/docs/theme/overview/index.mdx
  • apps/www/src/content/docs/theme/overview/props.ts
  • packages/raystack/components/theme-provider/__tests__/theme-scope.test.tsx
  • packages/raystack/components/theme-provider/index.tsx
  • packages/raystack/components/theme-provider/theme-scope.tsx
  • packages/raystack/components/theme-provider/theme.tsx
  • packages/raystack/components/theme-provider/types.ts
  • packages/raystack/index.tsx
  • packages/raystack/styles/colors.css
  • packages/raystack/styles/primitives/accent.css
  • packages/raystack/styles/primitives/appearance.css
  • packages/raystack/styles/primitives/gray.css

Comment on lines +17 to +23
const panelStyle = {
minWidth: 320,
padding: 'var(--rs-space-7)',
borderRadius: 'var(--rs-space-3)',
border: '1px solid var(--rs-color-border-base-primary)',
backgroundColor: 'var(--rs-color-background-base-primary)'
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a radius token for borderRadius here.

borderRadius is pulling from the spacing scale. That makes the example teach the wrong token family and can drift if spacing and radius scales stop lining up.

Suggested fix
 const panelStyle = {
   minWidth: 320,
   padding: 'var(--rs-space-7)',
-  borderRadius: 'var(--rs-space-3)',
+  borderRadius: 'var(--rs-radius-3)',
   border: '1px solid var(--rs-color-border-base-primary)',
   backgroundColor: 'var(--rs-color-background-base-primary)'
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const panelStyle = {
minWidth: 320,
padding: 'var(--rs-space-7)',
borderRadius: 'var(--rs-space-3)',
border: '1px solid var(--rs-color-border-base-primary)',
backgroundColor: 'var(--rs-color-background-base-primary)'
};
const panelStyle = {
minWidth: 320,
padding: 'var(--rs-space-7)',
borderRadius: 'var(--rs-radius-3)',
border: '1px solid var(--rs-color-border-base-primary)',
backgroundColor: 'var(--rs-color-background-base-primary)'
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/app/examples/scoped-theme/page.tsx` around lines 17 - 23, The
panelStyle object uses a spacing token for borderRadius (borderRadius:
'var(--rs-space-3)') which teaches the wrong token family; update the
borderRadius value to use the appropriate radius token (e.g., replace
var(--rs-space-3) with the project's radius token such as var(--rs-radius-3) or
the correct --rs-radius-<n>), keeping the rest of panelStyle unchanged so the
example demonstrates the proper radius token usage.

Comment on lines +13 to +20
/* Native UI (form controls, scrollbars, text selection) follows the scoped theme */
[data-theme="light"] {
color-scheme: light;
}

[data-theme="dark"] {
color-scheme: dark;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

enableColorScheme={false} is bypassed by unconditional scoped color-scheme.

ThemeProvider still models enableColorScheme as a conditional behavior (packages/raystack/components/theme-provider/theme.tsx, Line 40 and Lines 90-99 / 258-271), but this CSS now forces native color-scheme whenever data-theme is present. That makes the prop effectively non-functional.

Please gate these rules behind an explicit opt-in attribute/class so disabling enableColorScheme is still respected.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/styles/primitives/appearance.css` around lines 13 - 20, The
CSS unconditionally applies color-scheme when [data-theme] is present, bypassing
ThemeProvider's enableColorScheme flag; update the rules to only apply when an
explicit opt-in marker is present (e.g., [data-enable-color-scheme="true"] or a
class like .enable-color-scheme) so ThemeProvider's enableColorScheme prop in
ThemeProvider (packages/raystack/components/theme-provider/theme.tsx) can
control whether native color-scheme is set; ensure the marker is added/removed
by ThemeProvider where enableColorScheme is read so disabling the prop actually
prevents the CSS color-scheme rules from applying.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant